#!/usr/local/bin/python

##
## Description: # This script is used to validate the end of line (aka. EOL) in the files before
##              # the codes are checked in.
## Author:      # Ryan LIANG
## Date:        # July 19th, 2011
##

import os, re
import sys, getopt

CT_LSCO_STR = 'ct lscheckout -short -avobs -cview'
CT_TEXTMODE_STR = 'ct lsview -properties -text_mode -cview'
TEXTMODE_STR = 'TEXT MODE:'
UNIX_EOL = b'\n'
MSDOS_EOL = b'\r\n'
MAC_EOL = b'\r'
INVALID_EOL_RE = b'(\r\r)+\n'
UNIX_MODE = 'UNIX'
MSDOS_MODE = 'MSDOS'
TEXTMODE_TO_EOL = { UNIX_MODE: UNIX_EOL, MSDOS_MODE: MSDOS_EOL }

class MyError(Exception):
  def __init__(self, where, detail):
    self.message = '.....ERROR Occurs in %s: %s!' % (where, detail)
  def __str__(self):
    return repr(self.message)

class UsefulError(Exception):
  def __init__(self, message):
    self.message = '.....Oops!!! %s.' % message
  def __str__(self):
    return repr(self.message)

def exe_CMD(cmd_str):
  rslt = os.popen(cmd_str).readlines()
  return rslt

def get_textmode():
  for line in exe_CMD(CT_TEXTMODE_STR):
    if (line.upper().find(TEXTMODE_STR) != -1):      
      return line[line.upper().find(TEXTMODE_STR) + len(TEXTMODE_STR):].strip().upper()
  raise MyError('get_textmode()', 'Clearcase command string is: %s. Make sure you are under a view.' % CT_TEXTMODE_STR)

def override_file(file, new_data):
  try:
    file.seek(0)
    file.truncate()
    file.write(new_data)
    file.flush()
  except Exception as ex:
    raise MyError('override_file()', 'File = %s. %s.' % (file.name, str(ex)))

def validate_EOL(file, only_check, textmode):
  """ Checks if the EOL of file is not like \r\r\n, that is two or more \r followed by a \n.

  If the EOL of file is like \r\r\n, then it is not valid.

  Args:
    file: the File object.

  Returns:
    
  Raises:
  """
  try:
    file_data = file.read()
    invalid_match = re.search(INVALID_EOL_RE, file_data)
    if (invalid_match):
      warning_str = 'This file has invalid EOL, neither UNIX nor MSDOS type. The textmode of current view is %s' % textmode
      if (only_check):
        raise UsefulError('%s. Suggest you convert this file manually or turn on the switch by -c.' % warning_str)
      else:
        if (textmode == MSDOS_MODE):
          temp = re.sub(INVALID_EOL_RE, MSDOS_EOL, file_data)
        else:
          temp = re.sub(INVALID_EOL_RE, UNIX_EOL, file_data)
        override_file(file, temp)
        raise UsefulError('%s. But Sucessfully convert this file to %s type file.' % (warning_str, textmode))
  except UsefulError as ex:
    raise ex
  except Exception as ex:
    raise MyError('validate_EOL()', 'File = %s. %s.' % (file.name, str(ex)))

def replace_EOL(file, only_check, textmode):
  try:
    new_data = b''
    eol_dismatch = False
    file_lines = file.readlines()
    warning_str = 'Not all the EOLs of this file match with the textmode of current view: %s' % textmode
    for line in file_lines:
      eol_reget = b''
      if (line.endswith(UNIX_EOL) and (not line.endswith(MSDOS_EOL)) and textmode == MSDOS_MODE):
        eol_reget = UNIX_EOL
      elif (line.endswith(MSDOS_EOL) and textmode == UNIX_MODE):
        eol_reget = MSDOS_EOL
      if (eol_reget != b''):
        eol_dismatch = True
        if (only_check):
          raise UsefulError('%s. Suggest you convert this file manually or turn on the switch by -c.' % warning_str)
        else:
          new_data += line.replace(eol_reget, TEXTMODE_TO_EOL[textmode])
      else:
        new_data +=line
    if (not only_check and eol_dismatch):
      override_file(file, new_data)
      raise UsefulError('%s. But Sucessfully convert this file to %s type file.' % (warning_str, textmode))
  except UsefulError as ex:
    raise ex
  except Exception as ex:
    raise MyError('replace_EOL()', 'File = %s. %s.' % (file.name, str(ex)))

def Main(file_list, textmode, only_check = True):
  try:
    print('The Text Mode of Current View is: ' + textmode)
    if (len(file_list) == 0):
      print('No File is being checked!')
      return
    file_num = 1
    for each_file in file_list:
      fine = True
      try:
        each_file = each_file.strip()
        print('\n[FILE %d]: %s' % (file_num, each_file))
        file_num += 1        
        with open(each_file, 'r+b') as file:
          try:
            validate_EOL(file, only_check, textmode)
          except UsefulError as ex:
            fine = False
            print(str(ex))
            pass
          file.seek(0)
          try:
            replace_EOL(file, only_check, textmode)
          except UsefulError as ex:
            fine = False
            print(str(ex))
            pass
          file.close()
          if (fine):
            print('.....The file [ ' + each_file + ' ] is fine!')
      except IOError as ex:
        print('The file -%s cannot open!' % (each_file) + str(ex))
      except Exception as ex:
        print('Error occurs when processing file -%s. Message: %s' % (each_file, str(ex)))
  except Exception as ex:
    print('Error occurs when processing CheckCR. Message:' + str(ex))

def usage():
  print('python checkCR.py [-h | --help | -c | -r <files_root_dir>]')
  print('   -h: help information')
  print('   --help: help information')
  print('   -c: let the script do convert after checking')
  print('   -r <files_root_dir>: checks the files under <files_root_dir>')
    
if __name__ == '__main__':
  try:
    opts, args = getopt.getopt(sys.argv[1:], 'hcr:', ['help'])
  except getopt.GetoptError:
    usage()
    sys.exit(2)
  only_check = True
  files_dir = ''  
  for opt, arg in opts:
    if (opt in ('-h', '--help')):
      usage()
      sys.exit()
    elif (opt == '-c'):
      only_check = False
    elif (opt == '-r'):
      files_dir = arg
  try:
    if (files_dir != ''):  
      tmp_list = []
      for parent, dir_names, file_names in os.walk(files_dir):
        for file_name in file_names:
          tmp_list.append(os.path.join(parent, file_name))
      Main(tmp_list, MSDOS_MODE, only_check)
    else:
      Main(exe_CMD(CT_LSCO_STR), get_textmode(), only_check)
  except Exception as ex:
    print(str(ex))
##  print(GetTextMode())
